package birdsong.comm.implemented.tests;

import static org.junit.Assert.*;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.Random;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import birdsong.comm.ClientSideMessage;
import birdsong.comm.ServerSideMessage;
import birdsong.comm.implemented.ClientComm;
import birdsong.comm.implemented.ServerComm;

public class ServerCommTest {


	private static ClientComm clientComm;
	private static ServerComm serverComm;

	private static int MAXIMUM_NUMBER_OFF_CLIENTS=55;
	private static int LENGTH_OFF_NAMES=6;

	private static String clientUsername;

	private static int DEFAULT_TIMEOUT=100;

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {


	
		serverComm= new ServerComm();
		serverComm.start();
		clientComm= new ClientComm();
		clientUsername=getRandomNickName(LENGTH_OFF_NAMES);
		clientComm.connect("127.0.0.1",clientUsername);

	}


	@Test
	public void testSendAndRemoveFollowers() {
		LinkedList<String> followers = getRandomNames(1000,LENGTH_OFF_NAMES);

		assertTrue(followers!= null && followers.size()!=0);

		for (String string : followers) {
			clientComm.follow(string);
		}

		waitForSocketToStabilize(DEFAULT_TIMEOUT*10);

		int numberOffFollowers=0;
		while (serverComm.hasNextMessage()) {
			ServerSideMessage received =serverComm.getNextMessage();
			assertTrue(followers.contains(received.getPayload()) );
			assertEquals(received.getType(), ServerSideMessage.Type.FOLLOW);
			assertEquals(received.getClientNickname(),clientUsername );
			numberOffFollowers++;

		}
		assertEquals(numberOffFollowers, followers.size());

		for (String string : followers) {
			clientComm.unfollow(string);
		}
		waitForSocketToStabilize(DEFAULT_TIMEOUT);


		while (serverComm.hasNextMessage()) {
			ServerSideMessage received =serverComm.getNextMessage();
			assertTrue(followers.contains(received.getPayload()) );
			assertEquals(received.getType(), ServerSideMessage.Type.UNFOLLOW);
			followers.remove(received.getPayload());

		}

		assertTrue(followers.isEmpty());


	}


	@Test
	public void testSendError(){
		LinkedList<String> errors = getRandomNames(1000,24);

		for (String errorDescription : errors) {
			serverComm.sendError(clientUsername, errorDescription);
		}

		int messagesReceivedByClientCounter=0;
		waitForSocketToStabilize(DEFAULT_TIMEOUT);
		while(clientComm.hasNextMessage()){
			ClientSideMessage messageReceivedByClient = clientComm.getNextMessage();
			assertTrue(errors.contains(messageReceivedByClient.getPayload()));
			assertEquals(messageReceivedByClient.getSendersNickname(), ClientSideMessage.SERVER);
			assertEquals(messageReceivedByClient.getType(), ClientSideMessage.Type.ERROR);
			messagesReceivedByClientCounter++;
		}

		assertTrue(messagesReceivedByClientCounter==errors.size());
		
		serverComm.sendError("NAMENOTFOUND", "error");

	}

	@Test
	public void testSendClientConnectedAndClientDisconnected(){
		LinkedList<String> connecters = getRandomNames(1000,LENGTH_OFF_NAMES);

		assertTrue(connecters!= null && connecters.size()!=0);

		for (String string : connecters) {
			serverComm.sendClientConnected(clientUsername,string);
		}

		int messagesReceivedByClientCounter=0;
		waitForSocketToStabilize(DEFAULT_TIMEOUT);
		while(clientComm.hasNextMessage()){
			ClientSideMessage messageReceivedByClient = clientComm.getNextMessage();
			assertTrue(connecters.contains(messageReceivedByClient.getPayload()));
			assertEquals(messageReceivedByClient.getSendersNickname(), ClientSideMessage.SERVER);
			assertEquals(messageReceivedByClient.getType(), ClientSideMessage.Type.CONNECTED);

			messagesReceivedByClientCounter++;
		}

		assertTrue(messagesReceivedByClientCounter==connecters.size());

		for (String string : connecters) {
			serverComm.sendClientDisconnected(clientUsername,string);
		}

		waitForSocketToStabilize(DEFAULT_TIMEOUT);
		while(clientComm.hasNextMessage()){
			ClientSideMessage messageReceivedByClient = clientComm.getNextMessage();
			assertTrue(connecters.contains(messageReceivedByClient.getPayload()));
			assertEquals(messageReceivedByClient.getSendersNickname(), ClientSideMessage.SERVER);
			assertEquals(messageReceivedByClient.getType(), ClientSideMessage.Type.DISCONNECTED);

			connecters.remove(messageReceivedByClient.getPayload());
		}

		assertTrue(connecters.isEmpty());
		serverComm.sendClientDisconnected("NAMENOTFOUND","error");
		serverComm.sendClientConnected("NAMENOTFOUND","error");


	}
	
	
	@Test
	public void testSendFollowAndUnfollow(){
		LinkedList<String> connecters = getRandomNames(1000,LENGTH_OFF_NAMES);

		assertTrue(connecters!= null && connecters.size()!=0);

		for (String string : connecters) {
			serverComm.sendNewFollower(clientUsername, string);
		}

		int messagesReceivedByClientCounter=0;
		waitForSocketToStabilize(DEFAULT_TIMEOUT);
		while(clientComm.hasNextMessage()){
			ClientSideMessage messageReceivedByClient = clientComm.getNextMessage();
			assertTrue(connecters.contains(messageReceivedByClient.getPayload()));
			assertEquals(messageReceivedByClient.getSendersNickname(), ClientSideMessage.SERVER);
			assertEquals(messageReceivedByClient.getType(), ClientSideMessage.Type.FOLLOWER);
			messagesReceivedByClientCounter++;
		}

		assertTrue(messagesReceivedByClientCounter==connecters.size());

		for (String string : connecters) {
			serverComm.sendNewUnfollow(clientUsername, string);
		}

		waitForSocketToStabilize(DEFAULT_TIMEOUT);
		
		while(clientComm.hasNextMessage()){
			ClientSideMessage messageReceivedByClient = clientComm.getNextMessage();
			assertTrue(connecters.contains(messageReceivedByClient.getPayload()));
			assertEquals(messageReceivedByClient.getSendersNickname(), ClientSideMessage.SERVER);
			assertEquals(messageReceivedByClient.getType(), ClientSideMessage.Type.UNFOLLOWER);
			connecters.remove(messageReceivedByClient.getPayload());
		}

		assertTrue(connecters.isEmpty());
		serverComm.sendNewUnfollow("NAMENOTFOUND","error");
		serverComm.sendNewFollower("NAMENOTFOUND","error");


	}
	
	
	
	
	
	
	

	@Test
	public void testMultipleClientsSendingFollowers() throws UnknownHostException, IOException, InterruptedException{

		final Thread [] clients = new Thread[MAXIMUM_NUMBER_OFF_CLIENTS];
		final int numberOfMessagesPerClient=1000;
		final LinkedList<ClientComm> clientsComms = new LinkedList<ClientComm>();
		
		for ( int i = 0; i < MAXIMUM_NUMBER_OFF_CLIENTS; i++) {
			Thread t = new Thread(new Runnable() {

				@Override
				public void run() {
					try {
						ClientComm client= new ClientComm();
						client.connect("127.0.0.1", getRandomNickName(LENGTH_OFF_NAMES));
						clientsComms.add(client);
						
						for (int j = 0; j < numberOfMessagesPerClient; j++) {
							client.follow(getRandomNickName(LENGTH_OFF_NAMES));
						}
					} catch (UnknownHostException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					}

				}
			});
			clients[i]=t;
		}

		for (int i = 0; i < MAXIMUM_NUMBER_OFF_CLIENTS; i++) {
			clients[i].start();
		}
		for (int i = 0; i < MAXIMUM_NUMBER_OFF_CLIENTS; i++) {
			clients[i].join();
		}

		waitForSocketToStabilize(DEFAULT_TIMEOUT*20);

		int counter=0;
		while (serverComm.hasNextMessage()) {
			ServerSideMessage received =serverComm.getNextMessage();
			counter++;

		}


		assertTrue(counter==MAXIMUM_NUMBER_OFF_CLIENTS*numberOfMessagesPerClient);
		
		for (ClientComm clientComm : clientsComms) {
			clientComm.disconnect();
			assertFalse(clientComm.isConnected());

		}


	}



	@Test
	public void testSendMessage() throws UnknownHostException, IOException{

		LinkedList<String> clients = getRandomNames(MAXIMUM_NUMBER_OFF_CLIENTS,LENGTH_OFF_NAMES);
		final ClientComm [] clientsComms = new ClientComm[MAXIMUM_NUMBER_OFF_CLIENTS];
		String message = getRandomMessage();

		int index=0;
		for (String string : clients) {
			clientsComms[index]  = new ClientComm();
			clientsComms[index].connect("127.0.0.1", string);
			index++;
		}


		//Now each client send a message for each one of the other clients		
		for (String clientName  : clients) {
			for (int i = 0; i < clientsComms.length; i++) {
				serverComm.sendBirdsongMessage(clientName, clientsComms[i].getNickname(), message);
			}
		}

		waitForSocketToStabilize(DEFAULT_TIMEOUT*10);

		//Now each clientCommMust have @MAXIMUM_NUMBER_OFF_CLIENTS messages
		for (ClientComm clientComm : clientsComms) {
			int counter=0;
			while (clientComm.hasNextMessage()) {
				counter++;
				ClientSideMessage messageReceivedByClient = clientComm.getNextMessage();
				assertEquals(message, messageReceivedByClient.getPayload());
				assertTrue(clients.contains(messageReceivedByClient.getSendersNickname()));
			}
			assertTrue(counter==MAXIMUM_NUMBER_OFF_CLIENTS);
		}


		for (ClientComm clientComm : clientsComms) {
			clientComm.disconnect();
			//assertTrue(clientComm.isConnected()==false);
		}
		for (ClientComm clientComm : clientsComms) {
			assertTrue(clientComm.isConnected()==false);
		}
		
		serverComm.sendBirdsongMessage("NAMENOTFOUND", "NAMENOTFOUND", "NAMENOTFOUND");

	}

	@Test
	public void testClientConnectedCheck() throws UnknownHostException, IOException{
		final LinkedList<ClientComm> clientsComms = new LinkedList<ClientComm>();
		LinkedList<String> connectedClients = getRandomNames(1000,LENGTH_OFF_NAMES);

		for (String string : connectedClients) {
			ClientComm c = new ClientComm();
			c.connect("127.0.0.1", string);
			clientsComms.add(c);
		}
		waitForSocketToStabilize(DEFAULT_TIMEOUT*30);

		for (String string : connectedClients) {
			assertTrue(serverComm.clientIsConnected(string));
		}
		
		for (ClientComm clients : clientsComms) {
			clients.disconnect();
		}
		waitForSocketToStabilize(DEFAULT_TIMEOUT*30);

		for (String string : connectedClients) {
			assertFalse(serverComm.clientIsConnected(string));
		}
		
		
	}

	
	@Test
	public void dummyTestForToStrings(){
		
		ServerSideMessage message = new birdsong.comm.implemented.ServerSideMessage("payload", "nickName", ServerSideMessage.Type.BIRDSONG_MESSAGE);
		String temp = message.toString();
		ClientSideMessage message2 = new birdsong.comm.implemented.ClientSideMessage("payload", "nickName", ClientSideMessage.Type.BIRDSONG_MESSAGE);
		 temp = message2.toString();

		
	}
	
	@AfterClass
	public static void cleanUp(){
		clientComm.disconnect();
		serverComm.terminate();
	}

	private void waitForSocketToStabilize(int timeout) {
		try {
			Thread.sleep(timeout);
		} catch(InterruptedException ex) {
			Thread.currentThread().interrupt();
		}

	}



	private LinkedList<String> getRandomNames(int size,int wordSize){

		LinkedList<String> palavras = new LinkedList<String>();
		for (int i = 0; i < size; i++) {			
			palavras.add(getRandomNickName(wordSize));
		}
		return palavras;
	}

	private static String getRandomNickName(int wordSize){

		Random r = new Random();
		int rmin= 'A';
		int rmax='z';
		char[] word = new char[wordSize];
		for (int j = 0; j < wordSize; j++) {
			word[j]= (char) (rmin+ r.nextInt(rmax-rmin));
		}

		return new String(word);

	}

	private String getRandomMessage(){

		Random r = new Random();
		int wordSize=260;
		int rmin= 'A';
		int rmax='z';
		char[] word = new char[wordSize];
		for (int j = 0; j < wordSize; j++) {
			word[j]= (char) (rmin+ r.nextInt(rmax-rmin));
		}

		return new String(word);

	}


}
